//
//  TTDiskCache.m
//  Tint
//
//  Created by Kevin Wu on 8/23/12.
//  Copyright (c) 2012 Telligenty. All rights reserved.
//

#import "TTDiskCache.h"


@interface TTDiskCache (Private)

- (BOOL)fileExistsAtPath:(NSString *)path;
- (NSDictionary *)attributesOfFileAtPath:(NSString *)path;

- (void)saveCacheDictionary;
- (void)loadCacheDictionary;
- (BOOL)createRootDirectory;

@end

@implementation TTDiskCache (Private)

- (BOOL)fileExistsAtPath:(NSString *)path
{
    if ( path ) {
        BOOL isDirectory = NO;
        if ( [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory] ) {
            if ( !isDirectory ) {
                return YES;
            }
        } else {
            return NO;
        }
    }
    return NO;
}

- (NSDictionary *)attributesOfFileAtPath:(NSString *)path
{
    if ( path ) {
        NSError *error = nil;
        NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&error];
        if ( error == nil ) {
            return attributes;
        }
    }
    return nil;
}



- (void)saveCacheDictionary
{
    [_cacheDictionary writeToFile:[[self class] profilePath] atomically:YES];
}

- (void)loadCacheDictionary
{
    if ( _cacheDictionary == nil ) {
        
        NSMutableDictionary *newCacheDictionary = [[NSMutableDictionary alloc] init];
        _cacheDictionary = newCacheDictionary;
        
        
        NSString *profilePath = [[self class] profilePath];
        NSDictionary *cacheDictionary = [[NSDictionary alloc] initWithContentsOfFile:profilePath];
        
        for ( NSString *name in [cacheDictionary keyEnumerator] ) {
            
            NSDictionary *cache = [cacheDictionary objectForKey:name];
            
            if ( [cache count] > 0 ) {
                
                NSMutableDictionary *newCache = [[NSMutableDictionary alloc] init];
                
                for ( NSString *key in [cache keyEnumerator] ) {
                    
                    NSDictionary *item = [cache objectForKey:key];
                    
                    if (([item count] > 0) && 
                        [self isItemValid:item])
                    {
                        NSMutableDictionary *newItem = [[NSMutableDictionary alloc] init];
                        
                        for ( NSString *attribute in [item keyEnumerator] ) {
                            id value = [item objectForKey:attribute];
                            [newItem setObject:value forKey:attribute];
                        }
                        
                        [newCache setObject:newItem forKey:key];
                        TTRelease(newItem);
                        
                    } else {
                        NSString *filePath = [item objectForKey:TTCacheItemFilePath];
                        [[NSFileManager defaultManager] removeItemAtPath:filePath error:NULL];
                    }
                    
                }
                
                [newCacheDictionary setObject:newCache forKey:name];
                TTRelease(newCache);
                
            } else {
                NSString *cacheRoot = [[self class] cacheRootDirectory:name];
                [[NSFileManager defaultManager] removeItemAtPath:cacheRoot error:NULL];
            }
            
        }
        
        TTRelease(cacheDictionary);
        
    }
}

- (BOOL)createRootDirectory
{
    NSString *root = [[self class] rootDirectory];
    NSFileManager *fileManager = [NSFileManager defaultManager];
    BOOL isDirectory = NO;
    if ( ![fileManager fileExistsAtPath:root isDirectory:&isDirectory] ) {
        return [fileManager createDirectoryAtPath:root
                      withIntermediateDirectories:YES
                                       attributes:nil
                                            error:NULL];
    }
    return isDirectory;
}

@end




@implementation TTDiskCache

TTSynthesizeSingleton(TTDiskCache);


#pragma mark - Memory

- (id)init
{
    self = [super init];
    if ( self ) {
        [self createRootDirectory];
        [self loadCacheDictionary];
        [self saveCacheDictionary];
    }
    return self;
}

- (void)dealloc
{
    [super dealloc];
}


#pragma mark - Override


- (void)removeCacheForKey:(NSString *)key name:(NSString *)name
{
    [_executeLock lock];
    
    NSMutableDictionary *cache = [_cacheDictionary objectForKey:name];
    NSDictionary *item = [cache objectForKey:key];
    if ( item ) {
        NSString *filePath = [item objectForKey:TTCacheItemFilePath];
        [[NSFileManager defaultManager] removeItemAtPath:filePath error:NULL];
        [cache removeObjectForKey:key];
        
        if ( [cache count] < 1 ) {
            NSString *cacheRoot = [[self class] cacheRootDirectory:name];
            [[NSFileManager defaultManager] removeItemAtPath:cacheRoot error:NULL];
            [_cacheDictionary removeObjectForKey:name];
        }
        
        [self saveCacheDictionary];
    }
    
    [_executeLock unlock];
}



- (void)cleanAllCache
{
    [_executeLock lock];
    
    BOOL hasChanges = NO;
    
    NSArray *names = [_cacheDictionary allKeys];
    for ( int i=0; i<[names count]; ++i ) {
        NSString *name = [names objectAtIndex:i];
        NSMutableDictionary *cache = [_cacheDictionary objectForKey:name];
        
        NSArray *keys = [cache allKeys];
        for ( int j=0; j<[keys count]; ++j ) {
            NSString *key = [keys objectAtIndex:j];
            NSDictionary *item = [cache objectForKey:key];
            
            if ( ![self isItemValid:item] ) {
                hasChanges = YES;
                NSString *filePath = [item objectForKey:TTCacheItemFilePath];
                [[NSFileManager defaultManager] removeItemAtPath:filePath error:NULL];
                [cache removeObjectForKey:key];
            }
        }
        
        if ( [cache count] < 1 ) {
            hasChanges = YES;
            NSString *cacheRoot = [[self class] cacheRootDirectory:name];
            [[NSFileManager defaultManager] removeItemAtPath:cacheRoot error:NULL];
            [_cacheDictionary removeObjectForKey:name];
        }
    }
    
    if ( hasChanges ) {
        [self saveCacheDictionary];
    }
    
    [_executeLock unlock];
}

- (void)cleanCacheByName:(NSString *)name
{
    [_executeLock lock];
    
    NSMutableDictionary *cache = [_cacheDictionary objectForKey:name];
    if ( cache ) {
        
        BOOL hasChanges = NO;
        
        NSArray *keys = [cache allKeys];
        for ( int j=0; j<[keys count]; ++j ) {
            NSString *key = [keys objectAtIndex:j];
            NSDictionary *item = [cache objectForKey:key];
            
            if ( ![self isItemValid:item] ) {
                hasChanges = YES;
                NSString *filePath = [item objectForKey:TTCacheItemFilePath];
                [[NSFileManager defaultManager] removeItemAtPath:filePath error:NULL];
                [cache removeObjectForKey:key];
            }
        }
        
        if ( [cache count] < 1 ) {
            hasChanges = YES;
            NSString *cacheRoot = [[self class] cacheRootDirectory:name];
            [[NSFileManager defaultManager] removeItemAtPath:cacheRoot error:NULL];
            [_cacheDictionary removeObjectForKey:name];
        }
        
        if ( hasChanges ) {
            [self saveCacheDictionary];
        }
        
    }
    
    [_executeLock unlock];
}



- (void)clearAllCache
{
    [_executeLock lock];
    
    if ( [_cacheDictionary count] > 0 ) {
        for ( NSString *name in [_cacheDictionary keyEnumerator] ) {
            NSString *cacheRoot = [[self class] cacheRootDirectory:name];
            [[NSFileManager defaultManager] removeItemAtPath:cacheRoot error:NULL];
        }
        
        [_cacheDictionary removeAllObjects];
        [self saveCacheDictionary];
    }
    
    [_executeLock unlock];
}

- (void)clearCacheByName:(NSString *)name
{
    [_executeLock lock];
    
    if ( [_cacheDictionary hasKeyEqualTo:name] ) {
        NSString *cacheRoot = [[self class] cacheRootDirectory:name];
        [[NSFileManager defaultManager] removeItemAtPath:cacheRoot error:NULL];
        [_cacheDictionary removeObjectForKey:name];
        [self saveCacheDictionary];
    }
    
    [_executeLock unlock];
}



- (BOOL)createCacheForName:(NSString *)name
{
    if ( [super createCacheForName:name] ) {
        return [[self class] createCacheRootDirectory:name];
    }
    return NO;
}


#pragma mark - Public

- (NSString *)newStringForKey:(NSString *)key
{
    return [self newStringForKey:key name:TTCacheNameDefault];
}

- (NSString *)newStringForKey:(NSString *)key name:(NSString *)name
{
    NSString *string = nil;
    
    NSData *data = [self newDataForKey:key name:name];
    if ( data ) {
        string = [data newStringUsingUTF8StringEncoding];
    }
    TTRelease(data);
    
    return string;
}


- (NSData *)newDataForKey:(NSString *)key
{
    return [self newDataForKey:key name:TTCacheNameDefault];
}

- (NSData *)newDataForKey:(NSString *)key name:(NSString *)name
{
    if ( [self hasCacheForKey:key name:name] ) {
        NSString *filePath = [[self class] cachePathForKey:key name:name];
        return [[NSData alloc] initWithContentsOfFile:filePath];
	}
    return nil;
}




- (BOOL)setString:(NSString *)string forKey:(NSString *)key
{
    return [self setString:string forKey:key withTimeoutInterval:_defaultTimeoutInterval name:TTCacheNameDefault];
}

- (BOOL)setString:(NSString *)string forKey:(NSString *)key name:(NSString *)name
{
    return [self setString:string forKey:key withTimeoutInterval:_defaultTimeoutInterval name:name];
}

- (BOOL)setString:(NSString *)string forKey:(NSString *)key withTimeoutInterval:(NSTimeInterval)timeoutInterval
{
    return [self setString:string forKey:key withTimeoutInterval:timeoutInterval name:TTCacheNameDefault];
}

- (BOOL)setString:(NSString *)string forKey:(NSString *)key withTimeoutInterval:(NSTimeInterval)timeoutInterval name:(NSString *)name
{
    NSData *data = [string newDataUsingUTF8StringEncoding];
    BOOL status = [self setData:data forKey:key withTimeoutInterval:timeoutInterval name:name];
    TTRelease(data);
    
    return status;
}


- (BOOL)setData:(NSData *)data forKey:(NSString *)key
{
    return [self setData:data forKey:key withTimeoutInterval:_defaultTimeoutInterval name:TTCacheNameDefault];
}

- (BOOL)setData:(NSData *)data forKey:(NSString *)key name:(NSString *)name
{
    return [self setData:data forKey:key withTimeoutInterval:_defaultTimeoutInterval name:name];
}

- (BOOL)setData:(NSData *)data forKey:(NSString *)key withTimeoutInterval:(NSTimeInterval)timeoutInterval
{
    return [self setData:data forKey:key withTimeoutInterval:timeoutInterval name:TTCacheNameDefault];
}

- (BOOL)setData:(NSData *)data forKey:(NSString *)key withTimeoutInterval:(NSTimeInterval)timeoutInterval name:(NSString *)name
{
    if (TTIsStringWithText(key) && 
        TTIsStringWithText(name))
    {
        BOOL result = NO;
        
        [_executeLock lock];
        
        if ( [self createCacheForName:name] ) {
            NSString *filePath = [[self class] cachePathForKey:key name:name];
            
            NSMutableDictionary *item = [self addItemWithKey:key name:name timeoutInterval:timeoutInterval];
            
            if ( [data length] > 0 ) {
                NSNumber *fileSize = [[NSNumber alloc] initWithUnsignedInteger:[data length]];
                [item setObject:fileSize forKey:TTCacheItemFileSize];
                TTRelease(fileSize);
            } else {
                [item removeObjectForKey:TTCacheItemFileSize];
            }
            
            if ( data ) {
                [item setObject:filePath forKey:TTCacheItemFilePath];
            } else {
                [item removeObjectForKey:TTCacheItemFilePath];
            }
            [self saveCacheDictionary];
            
            if ( data ) {
                result = [data writeToFile:filePath atomically:YES];
            } else {
                result = YES;
            }
        }
        
        [_executeLock unlock];
        
        return result;
    }
    return NO;
}


- (BOOL)copyFileAtPath:(NSString *)filePath asKey:(NSString *)key
{
    return [self copyFileAtPath:filePath asKey:key withTimeoutInterval:_defaultTimeoutInterval name:TTCacheNameDefault];
}

- (BOOL)copyFileAtPath:(NSString *)filePath asKey:(NSString *)key name:(NSString *)name
{
    return [self copyFileAtPath:filePath asKey:key withTimeoutInterval:_defaultTimeoutInterval name:name];
}

- (BOOL)copyFileAtPath:(NSString *)filePath asKey:(NSString *)key withTimeoutInterval:(NSTimeInterval)timeoutInterval
{
    return [self copyFileAtPath:filePath asKey:key withTimeoutInterval:timeoutInterval name:TTCacheNameDefault];
}

- (BOOL)copyFileAtPath:(NSString *)filePath asKey:(NSString *)key withTimeoutInterval:(NSTimeInterval)timeoutInterval name:(NSString *)name
{
    if (TTIsStringWithText(key) && 
        TTIsStringWithText(name) && 
        [self fileExistsAtPath:filePath])
    {
        
        BOOL result = NO;
        
        [_executeLock lock];
        
        if ( [self createCacheForName:name] ) {
            NSString *finalPath = [[self class] cachePathForKey:key name:name];
            
            NSMutableDictionary *item = [self addItemWithKey:key name:name timeoutInterval:timeoutInterval];
            
            NSDictionary *attributes = [self attributesOfFileAtPath:filePath];
            if ( [attributes fileSize] > 0 ) {
                NSNumber *fileSize = [[NSNumber alloc] initWithUnsignedInteger:[attributes fileSize]];
                [item setObject:fileSize forKey:TTCacheItemFileSize];
                TTRelease(fileSize);
            } else {
                [item removeObjectForKey:TTCacheItemFileSize];
            }
            
            [item setObject:finalPath forKey:TTCacheItemFilePath];
            [self saveCacheDictionary];
            
            if ( ![finalPath isEqualToString:filePath] ) {
                result = [[NSFileManager defaultManager] copyItemAtPath:filePath toPath:finalPath error:NULL];
            } else {
                result = YES;
            }
        }
        
        [_executeLock unlock];
        
        return result;
        
    }
    return NO;
}


- (BOOL)moveFileAtPath:(NSString *)filePath asKey:(NSString *)key
{
    return [self moveFileAtPath:filePath asKey:key withTimeoutInterval:_defaultTimeoutInterval name:TTCacheNameDefault];
}

- (BOOL)moveFileAtPath:(NSString *)filePath asKey:(NSString *)key name:(NSString *)name
{
    return [self moveFileAtPath:filePath asKey:key withTimeoutInterval:_defaultTimeoutInterval name:name];
}

- (BOOL)moveFileAtPath:(NSString *)filePath asKey:(NSString *)key withTimeoutInterval:(NSTimeInterval)timeoutInterval
{
    return [self moveFileAtPath:filePath asKey:key withTimeoutInterval:timeoutInterval name:TTCacheNameDefault];
}

- (BOOL)moveFileAtPath:(NSString *)filePath asKey:(NSString *)key withTimeoutInterval:(NSTimeInterval)timeoutInterval name:(NSString *)name
{
    if (TTIsStringWithText(key) && 
        TTIsStringWithText(name) && 
        [self fileExistsAtPath:filePath])
    {
        
        BOOL result = NO;
        
        [_executeLock lock];
        
        if ( [self createCacheForName:name] ) {
            NSString *finalPath = [[self class] cachePathForKey:key name:name];
            
            NSMutableDictionary *item = [self addItemWithKey:key name:name timeoutInterval:timeoutInterval];
            
            NSDictionary *attributes = [self attributesOfFileAtPath:filePath];
            if ( [attributes fileSize] > 0 ) {
                NSNumber *fileSize = [[NSNumber alloc] initWithUnsignedInteger:[attributes fileSize]];
                [item setObject:fileSize forKey:TTCacheItemFileSize];
                TTRelease(fileSize);
            } else {
                [item removeObjectForKey:TTCacheItemFileSize];
            }
            
            [item setObject:finalPath forKey:TTCacheItemFilePath];
            [self saveCacheDictionary];
            
            if ( ![finalPath isEqualToString:filePath] ) {
                result = [[NSFileManager defaultManager] moveItemAtPath:filePath toPath:finalPath error:NULL];
            } else {
                result = YES;
            }
        }
        
        [_executeLock unlock];
        
        return result;
    }
    return NO;
}



- (int)totalCacheSize
{
    int size = 0;
    [_executeLock lock];
    for ( NSDictionary *cache in [_cacheDictionary objectEnumerator] ) {
        for ( NSDictionary *item in [cache objectEnumerator] ) {
            size += [[item objectForKey:TTCacheItemFileSize] intValue];
        }
    }
    [_executeLock unlock];
    return size;
}

- (int)cacheSize
{
    return [self cacheSizeOfName:TTCacheNameDefault];
}

- (int)cacheSizeOfName:(NSString *)name
{
    int size = 0;
    [_executeLock lock];
    NSDictionary *cache = [_cacheDictionary objectForKey:name];
    for ( NSDictionary *item in [cache objectEnumerator] ) {
        size += [[item objectForKey:TTCacheItemFileSize] intValue];
    }
    [_executeLock unlock];
    return size;
}



+ (NSString *)rootDirectory
{
    static NSString *directory = nil;
    if ( directory == nil ) {
        directory = [[TTCachesPath() stringByAppendingPathComponent:@"Tint"] copy];
    }
    return directory;
}

+ (NSString *)profilePath
{
    static NSString *directory = nil;
    if ( directory == nil ) {
        NSString *root = [[self class] rootDirectory];
        directory = [[root stringByAppendingPathComponent:@"CacheProfile.xml"] copy];
    }
    return directory;
}

+ (NSString *)cacheRootDirectory:(NSString *)name
{
    NSString *root = [self rootDirectory];
    return [root stringByAppendingPathComponent:name];
}

+ (BOOL)createCacheRootDirectory:(NSString *)name
{
    NSString *cacheRoot = [self cacheRootDirectory:name];
    NSFileManager *fileManager = [NSFileManager defaultManager];
    BOOL isDirectory = NO;
    if ( ![fileManager fileExistsAtPath:cacheRoot isDirectory:&isDirectory] ) {
        return [fileManager createDirectoryAtPath:cacheRoot
                      withIntermediateDirectories:YES
                                       attributes:nil
                                            error:NULL];
    }
    return isDirectory;
}

+ (NSString *)cachePathForKey:(NSString *)key
{
    return [self cachePathForKey:key name:TTCacheNameDefault];
}

+ (NSString *)cachePathForKey:(NSString *)key name:(NSString *)name
{
    NSString *cacheRoot = [self cacheRootDirectory:name];
    return [cacheRoot stringByAppendingPathComponent:key];
}

@end
